
import java.util.ArrayList;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;

/**
 *
 * @author Eddie
 */
public class DragonFractal {

    private final int WIDTH = 1900;
    private final int HEIGHT = 1000;
    // For 1080p, use 400, 1350, and 330 for START_X1, START_X2, and START_Y respectively
    private final int START_X1 = 400;
    private final int START_X2 = 1350;
    private final int START_Y = 330;

    private long lastFrame;

    public static void main(String[] args) {
        DragonFractal fractal = new DragonFractal();
        fractal.draw(true);
    }

    /**
     * Constructs a DragonFractal object, initializing OpenGL
     */
    public DragonFractal() {
        initGL();
    }

    /**
     * Sets up OpenGL
     */
    private void initGL() {
        try {
            Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT));
            Display.create();
        } catch (LWJGLException e) {
            e.printStackTrace();
            System.exit(0);
        }
        glColor3f(.5f, .2f, .7f);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, WIDTH, 0, HEIGHT, 1, -1);
        glMatrixMode(GL_MODELVIEW);
    }

    /**
     * Draws a line between point1 and point2
     *
     * @param point1 array with {x, y} values of point1
     * @param point2 array with {x, y} values of point2
     */
    private void drawLine(double[] point1, double[] point2) {
        glBegin(GL_LINES);
        glVertex2d(point1[0], point1[1]);
        glVertex2d(point2[0], point2[1]);
        glEnd();
    }

    /**
     * Draws the dragon fractal 17 times, showing each iteration
     */
    public void draw(boolean clearScreen) {
        long elapsedTime = 0;
        int count = 0;
        while (!Display.isCloseRequested()) {
            elapsedTime += getDelta();
            drawFractal(count, clearScreen);
            if (elapsedTime >= 1000 && count < 18) {
                count++;
                elapsedTime = 0;
            }
            Display.update();
        }
        Display.destroy();
    }

    /**
     * Draws an iteration of the fractal
     *
     * @param n iteration of the fractal
     * @param clearScreen set true to clear the screen before updating it
     */
    public void drawFractal(int n, boolean clearScreen) {
        ArrayList<double[]> points = new ArrayList();
        points.add(new double[]{START_X1, START_Y});
        points.add(new double[]{START_X2, START_Y});
        for (int i = 0; i < n; i++) {
            int sign = 1;
            for (int j = 0; j < points.size() - 1; j += 2, sign *= -1) {

                double dx = points.get(j + 1)[0] - points.get(j)[0];
                double dy = points.get(j + 1)[1] - points.get(j)[1];

                double x = Math.sqrt(dx * dx + dy * dy) / Math.sqrt(2)
                        * Math.cos((Math.atan2(dy, dx) + sign * Math.PI / 4)) + points.get(j)[0];
                double y = Math.sqrt(dx * dx + dy * dy) / Math.sqrt(2)
                        * Math.sin((Math.atan2(dy, dx) + sign * Math.PI / 4)) + points.get(j)[1];

                points.add(j + 1, new double[]{x, y});
            }

        }
        if (clearScreen) {
            glClear(GL_COLOR_BUFFER_BIT);
        }
        for (int i = 0; i < points.size() - 1; i++) {
            drawLine(points.get(i), points.get(i + 1));
        }
    }

    /**
     * Get the delta between each update
     *
     * @return delta
     */
    public int getDelta() {
        long time = getTime();
        int delta = (int) (time - lastFrame);
        lastFrame = time;

        return delta;

    }

    /**
     * Gets current time in milliseconds
     *
     * @return time in milliseconds
     */
    public long getTime() {
        return System.nanoTime() / 1000000;
    }

}
